/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { transBigInt32 } from './arg';
import { BasicBlock } from './basic_block';
import { StackSlot } from './stack_solt';
import { Tmp } from './tmp';
import { addIndexed, lowerSymbolName, roundUpToMultipleOf, stackAlignmentBytes } from './util';

const CODE_OPERATION_NUM_97: number = 97;
const CODE_OPERATION_NUM_7: number = 7;
const CODE_OPERATION_NUM_101: number = 101;
const CODE_OPERATION_NUM_MAX: number = 1000001;

export class Code {
  _blocks: BasicBlock[] = new Array();
  _stackSlots: StackSlot[] = new Array();
  _gpTmps: Tmp[] = new Array();
  _fpTmps: Tmp[] = new Array();
  _callArgAreaSize: number = 0;
  _frameSize: number = 0;

  addBasicBlock(frequency: number = 1): BasicBlock {
    let basicBlock = new BasicBlock(this._blocks.length, frequency);
    return addIndexed(this._blocks, basicBlock);
  }

  addStackSlot(byteSize: number, kind: string): StackSlot {
    let stackSlot = new StackSlot(this._stackSlots.length, byteSize, kind);
    return addIndexed(this._stackSlots, stackSlot);
  }

  newCodeTmp(type: string): Tmp {
    let str = lowerSymbolName(type);
    if (str === 'gp') {
      let tmp = new Tmp(this._gpTmps.length, type);
      return addIndexed(this._gpTmps, tmp);
    }

    if (str === 'fp') {
      let tmp = new Tmp(this._fpTmps.length, type);
      return addIndexed(this._fpTmps, tmp);
    }
    return new Tmp(0, type);
  }

  get size(): number {
    return this._blocks.length;
  }

  at(index: number): BasicBlock | null {
    if (index < this.size) {
      return this._blocks[index];
    }
    return null;
  }

  blocks(): BasicBlock[] {
    return this._blocks;
  }

  get stackSlots(): StackSlot[] {
    return this._stackSlots;
  }

  tmps(type: string): Tmp[] {
    if (type === 'gp') {
      return this._gpTmps;
    } else if (type === 'fp') {
      return this._fpTmps;
    } else {
      throw new Error('Invalid type');
    }
  }

  get callArgAreaSize(): number {
    return this._callArgAreaSize;
  }

  requestCallArgAreaSize(size: number): void {
    this._callArgAreaSize = Math.max(this._callArgAreaSize, roundUpToMultipleOf(stackAlignmentBytes, size));
  }

  get frameSize(): number {
    return this._frameSize;
  }

  setFrameSize(frameSize: number): void {
    this._frameSize = frameSize;
  }
  hash(): number {
    let result = 0;
    for (let block of this._blocks) {
      result *= CODE_OPERATION_NUM_MAX;
      result = transBigInt32(result);
      result |= 0;
      for (let inst of block.insts) {
        result *= CODE_OPERATION_NUM_97;
        result = transBigInt32(result);
        result |= 0;
        result += inst.hash();
        result = transBigInt32(result);
        result |= 0;
        // console.log("for block.insts result is", result)
      }
      for (let successor of block.successorBlocks()) {
        result *= CODE_OPERATION_NUM_7;
        result = transBigInt32(result);
        result |= 0;
        result += successor.index;
        result = transBigInt32(result);
        result |= 0;
        // console.log("for block.successorBlocks result is", result)
      }
    }
    for (let slot of this._stackSlots) {
      result *= CODE_OPERATION_NUM_101;
      result = transBigInt32(result);
      result |= 0;
      result += slot.hash();
      result = transBigInt32(result);
      result |= 0;
    }
    return result >>> 0;
  }

  toString(): string {
    let result = '';
    for (let block of this._blocks) {
      result += block.toStringDeep();
    }
    if (this._stackSlots.length > 0) {
      result += 'Stack slots:\n';
      for (let slot of this._stackSlots) {
        result += '    ' + slot._kind + '\n';
      }
    }
    if (this._frameSize !== 0) {
      result += `Frame size: ${this._frameSize}\n`;
    }
    if (this._callArgAreaSize !== 0) {
      result += `Call arg area size: ${this._callArgAreaSize}\n`;
    }
    return result;
  }

  [Symbol.iterator](): Iterator<BasicBlock> {
    return this._blocks[Symbol.iterator]();
  }
}
